package org.yagnus.datastructure;

/*
 * @author Hsb
 * 
 *  This class defines mathematical range on any totally ordered BASETYPE.
 *  Instances of this class are used to create non-intersecting sets or maps of ranges.
 *
 *  NOTE: equals(o) and comopareTo(o)==0 have different meaning.
 * 
 *         FIXME:The class allows only query by point or by range that is
 *         entirely contained in a specified range. Basically, need to figure
 *         out how to handle the case of closed range via joinable.
 * 
 *
 * TODO: Make available unbounded ranges.
 * 
 */
public class TotallyOrderedRange<BASETYPE extends Comparable<BASETYPE>> extends
		RangeImpl<BASETYPE> implements
		Comparable<TotallyOrderedRange<BASETYPE>>, Cloneable {

	protected TotallyOrderedRange(BASETYPE min, BASETYPE max,
			boolean openOnTheLeft, boolean openOnTheRight) {
		super(min, max, openOnTheLeft, openOnTheRight);
	}

	/**
	 * 
	 * This method tries to join the current range with the one passed in if
	 * successful, return a range to take on the range represented by this range
	 * and the other before call to join and return true. If join was
	 * unsuccessful, then false is returned
	 * 
	 * In case of tie, resolve by keeping my own setting.
	 * 
	 * @param o
	 * @return the range if join happened and null if not able to.
	 */
	public TotallyOrderedRange<BASETYPE> join(TotallyOrderedRange<BASETYPE> o) {

		BASETYPE retmin = min, retmax = max;
		boolean retol = openOnTheLeft, retor = openOnTheRight;
		/* in case of joining touch open and closed sets: */
		if (min.equals(o.max) && openOnTheLeft ^ o.openOnTheRight) {
			retmin = o.min;
			retol = o.openOnTheLeft;
			return new TotallyOrderedRange<BASETYPE>(retmin, retmax, retol,
					retor);
		}

		if (max.equals(o.min) && openOnTheRight ^ o.openOnTheLeft) {
			retmax = o.max;
			retor = o.openOnTheRight;
			return new TotallyOrderedRange<BASETYPE>(retmin, retmax, retol,
					retor);
		}

		// Otherwise join only if we intersect
		if (this.compareTo(o) == 0) {
			if (o.min.compareTo(min) < 0) {
				retol = o.openOnTheLeft;
				retmin = o.min;

			}// otherwise keep my min and my openness

			if (o.max.compareTo(max) > 0) {
				retor = o.openOnTheRight;
				retmax = o.max;
			}

			return new TotallyOrderedRange<BASETYPE>(retmin, retmax, retol,
					retor);
		} else {
			return null;
		}
	}

	/**
	 * 
	 * return an ordering on the ranges: intersection implies equality otherwise
	 * return if o is left (1) or right(-1) of this object
	 * 
	 * @param o
	 *            the other one
	 */
	public final int compareTo(TotallyOrderedRange<BASETYPE> o) {

		// if the object I'm being compared to is null, by convention return
		// that I'm bigger.
		if (o == null) {
			return 1;
		}

		// test for raw equals
		if (openOnTheLeft == o.openOnTheLeft
				&& openOnTheRight == o.openOnTheRight && min.equals(o.min)
				&& max.equals(o.max)) {
			return 0;
		}

		// test for one
		int compare = o.max.compareTo(min);
		if (openOnTheLeft || o.openOnTheRight) {
			if (compare <= 0) {
				return 1;
			}
		} else {
			if (compare < 0) {
				return 1;
			}
		}

		// test for minus one
		compare = o.min.compareTo(max);
		if (openOnTheRight || o.openOnTheLeft) {
			if (compare >= 0) {
				return -1;
			}
		} else {
			if (compare > 0) {
				return -1;
			}
		}

		// If neither -1 nor 1 then a lesser 0
		return 0;
	}

	@Override
	/*
	 * 
	 * note equals is not equivalent to compareTo definition of equals
	 * 
	 * @param o the other object
	 * 
	 * @return true if this range is strictly identical to the there one.
	 */
	public final boolean equals(Object o) {

		if (o == null) {
			return false;
		}

		if (o instanceof TotallyOrderedRange) {
			try {
				TotallyOrderedRange<BASETYPE> OO = (TotallyOrderedRange<BASETYPE>) o;
				return (openOnTheLeft == OO.openOnTheLeft
						&& openOnTheRight == OO.openOnTheRight
						&& min.equals(OO.min) && max.equals(OO.max));
			} catch (ClassCastException e) {
				return false;
			}
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		int hash = 7;
		hash = 59 * hash + (this.openOnTheLeft ? 1 : 0);
		hash = 59 * hash + (this.openOnTheRight ? 1 : 0);
		hash = 59 * hash + (this.min != null ? this.min.hashCode() : 0);
		hash = 59 * hash + (this.max != null ? this.max.hashCode() : 0);
		return hash;
	}

	/**
	 * 
	 * generates a point as a closed set on one point.
	 * 
	 * @param p
	 * @return
	 */
	public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getPoint(
			T_BASETYPE p) {
		return new TotallyOrderedRange<T_BASETYPE>(p, p, false, false);
	}

	/**
	 * 
	 * generates an open range as a closed set on one point.
	 * 
	 * @param p1
	 * @param p2
	 * @return the range, or null on error.
	 */
	public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getOpen(
			T_BASETYPE p1, T_BASETYPE p2) {
		if (p1.compareTo(p2) > 0) {
			return null;
		}
		return new TotallyOrderedRange<T_BASETYPE>(p1, p2, true, true);
	}

	/**
	 * 
	 * generates a range that is open on the left close on the right
	 * 
	 * @param p1
	 * @param p2
	 * @return the range, or null on error.
	 */
	public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getOpenLeft(
			T_BASETYPE p1, T_BASETYPE p2) {
		if (p1.compareTo(p2) > 0) {
			return null;
		}
		return new TotallyOrderedRange<T_BASETYPE>(p1, p2, true, false);
	}

	/**
	 * 
	 * generates a range that is open on the right closed on the left
	 * 
	 * @param p1
	 * @param p2
	 * @return the range, or null on error.
	 */
	public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getOpenRight(
			T_BASETYPE p1, T_BASETYPE p2) {
		if (p1.compareTo(p2) > 0) {
			return null;
		}
		return new TotallyOrderedRange<T_BASETYPE>(p1, p2, false, true);
	}

	/**
	 * 
	 * generates a range that is closed on both ends;
	 * 
	 * @param p1
	 * @param p2
	 * @return the range, or null on error.
	 */
	public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getClosed(
			T_BASETYPE p1, T_BASETYPE p2) {
		if (p1.compareTo(p2) > 0) {
			return null;
		}
		return new TotallyOrderedRange<T_BASETYPE>(p1, p2, false, false);
	}

	@Override
	public String toString() {
		StringBuffer ret = new StringBuffer();
		if (openOnTheLeft) {
			ret.append("(");
		} else {
			ret.append("[");
		}
		ret.append(min);
		ret.append(',');
		ret.append(max);
		if (openOnTheRight) {
			ret.append(")");
		} else {
			ret.append("]");
		}

		return ret.toString();
	}
}
